package com.hoppinzq.servlet;

import com.hoppinzq.brap.auth.*;
import com.hoppinzq.brap.common.InputStreamArgumentPlaceholder;
import com.hoppinzq.brap.common.InvocationRequest;
import com.hoppinzq.brap.common.InvocationResponse;
import com.hoppinzq.brap.exception.RemotingException;
import com.hoppinzq.brap.modification.ChangesIgnoredModificationManager;
import com.hoppinzq.brap.modification.ModificationManager;
import com.hoppinzq.brap.service.ServiceWrapper;

import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * 此ProxyServlet是从web.xml为您想要的每个服务配置的
 * 公开为远程处理服务
 */
@WebServlet(urlPatterns = "/zc")
public class ProxyServlet implements Servlet {
    private static final Integer DEFAULT_STREAM_BUFFER_SIZE = 16384;

    public final String INIT_PARAM_AUTHENTICATION_PROVIDER = "authenticationProvider";
    public final String INIT_PARAM_AUTHORIZATION_PROVIDER = "authorizationProvider";
    public final String INIT_PARAM_MODIFICATION_MANAGER = "modificationManager";

    public final String INIT_PARAM_SERVICE = "service";
    protected ServiceWrapper serviceWrapper;
    protected ServletConfig servletConfig;

    public void init(ServletConfig servletConfig) throws ServletException {
        System.out.println("ProxyServlet初始化中。。。");
        this.servletConfig = servletConfig;
        try {
            //createServiceWrapper();

        } catch (Exception e) {
            throw new ServletException("Failed to instantiate the serviceWrapper", e);
        }
        System.out.println("ProxyServlet完毕。。。");
    }

    /**
     * Override this method to control every detail of the creation of the service wrapper.
     * <p/>
     * Normally you would just override one or more of the methods that provide the service wrapper details.
     * 重写此方法以控制创建服务包装的每个细节。
     * 通常，您只需重写一个或多个提供服务包装器详细信息的方法。
     * @see ProxyServlet#getService()
     * @see ProxyServlet#getAuthenticationProvider()
     * @see ProxyServlet#getAuthorizationProvider()
     */
    public void createServiceWrapper() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        System.out.println("no.tornado.brap.servlet.ProxyServlet创建包装服务。。。");
        serviceWrapper = new ServiceWrapper();
        serviceWrapper.setService(getService());
        serviceWrapper.setAuthenticationProvider(getAuthenticationProvider());
        serviceWrapper.setAuthorizationProvider(getAuthorizationProvider());
        serviceWrapper.setModificationManager(getModificationManager());
    }

    /**
     * Override to configure a different Authorization Provider. The default provider
     * authorizes every authenticated invocation. In many cases, requiring Authentication and providing
     * an AuthenticationProvider is sufficient, but you can use the Authorization Provider
     * to allow/deny access to spesific method-calls based on the principal in
     * <code>AuthenticationContext#getPrincipal()</code>.
     * <p/>
     * You can either subclass or supply the "authorizationProvider" init-param to
     * change the AuthorizationProvider.
     * 重写以配置不同的授权提供程序。默认提供程序
     *
     * *授权每个经过身份验证的调用。在许多情况下，需要身份验证并提供
     * @return the AuthorizationProvider
     */
    protected AuthorizationProvider getAuthorizationProvider() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        System.out.println("no.tornado.brap.servlet.ProxyServlet获取授权");
        if (servletConfig.getInitParameter(INIT_PARAM_AUTHORIZATION_PROVIDER) != null)
            return (AuthorizationProvider) Class.forName(servletConfig.getInitParameter(INIT_PARAM_AUTHORIZATION_PROVIDER)).newInstance();

        return new AuthenticationRequiredAuthorizer();
    }

    /**
     * Override to configure a different Authentication Provider. The default provider
     * authenticates every invocation.
     * <p/>
     * You can either subclass or supply the "authenticationProvider" init-param to
     * change the AuthenticationProvider.
     *
     * @return the AuthenticationProvider
     */
    protected AuthenticationProvider getAuthenticationProvider() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        System.out.println("no.tornado.brap.servlet.ProxyServlet获取授权");
        if (servletConfig.getInitParameter(INIT_PARAM_AUTHENTICATION_PROVIDER) != null)
            return (AuthenticationProvider) Class.forName(servletConfig.getInitParameter(INIT_PARAM_AUTHENTICATION_PROVIDER)).newInstance();

        return new AuthenticationNotRequiredAuthenticator();
    }

    /**
     * Supply the service to expose via this servlet.
     * 通过此servlet提供要公开的服务。
     * <p/>
     * You can either subclass or supply the "service" init-param to
     * configure what service class to instantiate.
     *您可以将“服务”init参数子类化或提供给
     *
     * *配置要实例化的服务类。
     * @return
     */
    protected Object getService() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        System.out.println("no.tornado.brap.servlet.ProxyServlet获取要公开的服务");
        return Class.forName(servletConfig.getInitParameter(INIT_PARAM_SERVICE)).newInstance();
    }

    /**
     * The service method performs the actual deserialization of the InvocationRequest and returns
     * an InvocationResponse in the body of the ServletResponse.
     * <p/>
     * Standard Java object serialization/deserialization is used to retrieve and set the invocation
     * request/response.
     * <p/>
     * The configured <code>AuthenticationProvider</code> and <code>AuthorizationProvider</code>
     * are consulted.
     * <p/>
     * A ThreadLocal in the <code>AuthenticationContext</code> holds on to any principal created during
     * authentication, so that it is available to both the AuthorizationProvider and any service
     * that whishes to get hold of the principal via <code>AuthenticationContext#getPrincipal()</code>.
     * <p/>
     * You are encouraged to use your existing domain object AllowAllAuthorizerfor authentication :)
     *
     * @param request  The ServletRequest
     * @param response the ServletResponse
     * @throws ServletException
     * @throws IOException
     * @see InvocationRequest
     * @see InvocationResponse
     */

    /**
     * 服务方法执行调用请求的实际反序列化并返回
     * *ServletResponse主体中的调用响应。
     * *<p/>
     * *标准Java对象序列化/反序列化用于检索和设置调用
     * *请求/答复。
     * *已配置的<code>AuthenticationProvider</code>和<code>AuthenticationProvider</code>
     * *征求意见。
     * *<p/>
     * *<code>AuthenticationContext</code>中的ThreadLocal保留在
     * *身份验证，以便AuthorizationProvider和任何服务都可以使用它
     * *它通过<code>AuthenticationContext#getPrincipal（）</code>获得主体。
     * *建议您使用现有域对象AllowAllAuthorizer进行身份验证：）
     * @param request
     * @param response
     * @throws ServletException
     * @throws IOException
     */
    public void service(ServletRequest request, ServletResponse response) throws ServletException,IOException {
        System.out.println(request.getClass().getName());
        System.out.println("服务方法执行调用请求的实际反序列化并返回"+System.currentTimeMillis());
        ((HttpServletResponse)response).setHeader("Access-Control-Allow-Origin", "*");
        ((HttpServletResponse)response).setHeader("Access-Control-Allow-Methods", "*");
        ((HttpServletResponse)response).setHeader("Access-Control-Max-Age", "4200");
        ((HttpServletResponse)response).setHeader("Access-Control-Allow-Headers", "*");
        ((HttpServletResponse)response).setHeader("Access-Control-Allow-Credentials", "true");
        AuthenticationContext.enter();
        InvocationResponse invocationResponse = null;
        InvocationRequest invocationRequest = null;
        Method method = null;
        Object result = null;
        try {
            invocationResponse = new InvocationResponse();
            System.out.println("开始获取InvocationResponse："+System.currentTimeMillis());
            ServletInputStream servletInputStream=request.getInputStream();
            System.err.println("准备好了？"+servletInputStream.isReady());
            invocationRequest = (InvocationRequest) new ObjectInputStream(servletInputStream).readObject();
            System.out.println("获取InvocationResponse完毕："+System.currentTimeMillis());

            serviceWrapper.getAuthenticationProvider().authenticate(invocationRequest);
            serviceWrapper.getAuthorizationProvider().authorize(invocationRequest);
            Object[] proxiedParameters = serviceWrapper.getModificationManager().applyModificationScheme(invocationRequest.getParameters());
            method = getMethod(invocationRequest.getMethodName(), invocationRequest.getParameterTypes());
            System.out.println("调用了服务的："+method.getName());
            // Reroute transformed input-stream
            if (invocationRequest.getParameters() != null) {
                for (int i = 0; i < invocationRequest.getParameters().length; i ++) {
                    if (invocationRequest.getParameters()[i] != null && InputStreamArgumentPlaceholder.class.equals(invocationRequest.getParameters()[i].getClass())) {
                        proxiedParameters[i] = request.getInputStream();
                        break;
                    }
                }
            }

            preMethodInvocation();
            System.out.println("开始执行服务的方法");
            result = method.invoke(serviceWrapper.getService(), proxiedParameters);
            invocationResponse.setResult((Serializable) result);
            invocationResponse.setModifications(serviceWrapper.getModificationManager().getModifications());

        } catch (Exception e) {
            if (e instanceof InvocationTargetException) {
                InvocationTargetException ite = (InvocationTargetException) e;
                invocationResponse.setException(ite.getTargetException());
            } else {
                if (method != null && method.getExceptionTypes() != null) {
                    for (Class exType : method.getExceptionTypes()) {
                        if (exType.isAssignableFrom(e.getClass()))
                            invocationResponse.setException(e);
                    }
                }
                invocationResponse.setException(new RemotingException(e));
            }
        } finally {
            AuthenticationContext.exit();
            postMethodInvocation();
            if (result != null && result instanceof InputStream) {
                streamResultToResponse(result, response);
            } else {
                try {
                    if (invocationRequest != null) {
                        ObjectOutputStream out = new ObjectOutputStream(response.getOutputStream());
                        out.writeObject(invocationResponse);
                        out.close();
                    } else {
                        respondWithInterfaceDeclaration(response);
                    }
                } catch (Exception e) {
                    InvocationResponse reporter = new InvocationResponse();
                    reporter.setException(new RuntimeException(e.getClass() + " error while writing result: " + e.getMessage()));

                    ObjectOutputStream out = new ObjectOutputStream(response.getOutputStream());
                    out.writeObject(reporter);
                    out.close();
                }
            }
        }
    }

    /**
     * No invocationRequest was available, assume request by something else than BRAP Client.
     * Return HTML describing the service interface
     * 没有可用的调用请求，假设请求不是由BRAP客户端发出的。
     * 返回描述服务接口的HTML
     * @param response
     */
    private void respondWithInterfaceDeclaration(ServletResponse response) throws IOException {
        System.out.println("开始打印接口文档:");
        StringBuilder s = new StringBuilder();
        s.append("<style type=\"text/css\">");
        s.append("table { width: 100%; border-collapse: collapse; border: 1px solid #ccc; }");
        s.append("td, th { padding: 5px; } ");
        s.append("td { border: 1px solid #ccc; margin: 0; }");
        s.append("th { text-align: left; background-color: #cce; }");
        s.append("td.returnType { text-align: right; }");
        s.append("</style>");
        s.append("<h1>" + serviceWrapper.getService().getClass().getSimpleName() + "</h1>");
        s.append("<table><tr><th colspan=\"2\">Method Summary</th></tr>");
        for (Method method : serviceWrapper.getService().getClass().getDeclaredMethods()) {
            s.append("<tr><td class=\"returnType\">" + method.getReturnType().getSimpleName() + "</td><td class=\"method\">");
            s.append("<strong>" + method.getName() + "</strong>(");
            Class[] params = method.getParameterTypes();
            if (params != null && params.length > 0) {
                for (int i = 0; i < params.length; i++) {
                    if (i > 0)
                        s.append(", ");
                    s.append(params[i].getSimpleName() + " arg" + i);
                }
            }
            s.append(")</td></tr>");
        }
        s.append("</table>");
        System.out.println("html是:"+s.toString());
        OutputStream out = response.getOutputStream();
        out.write(s.toString().getBytes());
        out.close();
    }

    /**
     * Overidde to do custom work after invocing a method on your service.
     * Overidde在对服务的方法进行开票后执行自定义工作
     */
    protected void postMethodInvocation() {
        System.err.println("在对服务的方法进行开票后执行自定义工作");
    }


    /**
     * Override to do custom work before invocting a method on your service,
     * for example setting a thread local value etc.
     * 重写以在服务上调用方法之前执行自定义工作，
     * *例如，设置线程本地值等。
     */
    protected void preMethodInvocation() {
        System.err.println("重写以在服务上调用方法之前执行自定义工作");
    }

    private void streamResultToResponse(Object result, ServletResponse response) throws IOException {
        InputStream in = (InputStream) result;
        OutputStream out = response.getOutputStream();
        byte[] buf = new byte[getStreamBufferSize()];
        int len;
        while ((len = in.read(buf)) != -1) {
            out.write(buf, 0, len);
        }
    }

    public ModificationManager getModificationManager() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        if (servletConfig.getInitParameter(INIT_PARAM_MODIFICATION_MANAGER) != null)
            return (ModificationManager) Class.forName(servletConfig.getInitParameter(INIT_PARAM_MODIFICATION_MANAGER)).newInstance();

        return new ChangesIgnoredModificationManager();
    }

    /**
     * Retrieves the <code>java.lang.Reflect.Method</code> to invoke on the wrapped service class.
     * The <code>methodName</code> and <code>parameterTypes</code> arguments are retrieved from the
     * <code>InvocationRequest</code> that was encapsulated in the ServletRequest body.
     * 检索<code>java.lang.Reflect.Method</code>以在包装好的服务类上调用。
     * *<code>methodName</code>和<code>parameterTypes</code>参数是从
     * *封装在ServletRequest正文中的调用请求。
     * @param methodName
     * @param parameterTypes
     * @return
     * @throws NoSuchMethodException
     */
    private Method getMethod(String methodName, Class[] parameterTypes) throws NoSuchMethodException {
        System.out.println("客户端要调用服务的方法是："+methodName);
        for(Class praameter:parameterTypes){
            System.out.println("方法的参数有："+praameter.getName());
        }
        Class serviceClass = serviceWrapper.getService().getClass();
        System.out.println("调用服务的实现类是："+serviceClass.getName());
        while (serviceClass != null) {
            try {
                return serviceClass.getMethod(methodName, parameterTypes);
            } catch (NoSuchMethodException e) {
                serviceClass = serviceClass.getSuperclass();
            }
        }

        throw new NoSuchMethodException(methodName);
    }

    public String getServletInfo() {
        return getClass().getCanonicalName();
    }

    public void destroy() {

    }

    public ServletConfig getServletConfig() {
        return servletConfig;
    }

    public Integer getStreamBufferSize() {
        return DEFAULT_STREAM_BUFFER_SIZE;
    }
}
